﻿using System;
using System.Diagnostics;
using System.IO;
using System.Security;
using NAnt.Core;
using NAnt.Core.Attributes;

namespace Lean.NantTasks.RunAs
{
    /// <summary>
    /// Similiar to the nant-exec-task. However credentials can be supplied to elevate user rights.
    /// Usefull to run Selenium/Webdriver-Unittests in IE 
    /// </summary>
    [TaskName("runas")]
    public class RunAsTask : Task 
    {
        [TaskAttribute("program"), StringValidator(AllowEmpty = false)]
        public string Program { get; set; }

        [TaskAttribute("commandline"), StringValidator(AllowEmpty = false)]
        public string Commandline { get; set; }

        [TaskAttribute("username"), StringValidator(AllowEmpty = false)]
        public string Username { get; set; }

        [TaskAttribute("password"), StringValidator(AllowEmpty = false)]
        public string Password { get; set; }

        [TaskAttribute("workingdir"), StringValidator(AllowEmpty = true)]
        public string WorkingDir { get; set; }


        private void Log(string message)
        {
            Project.Log(Level.Info, message);
        }

        
        private string GetFullpath(string p)
        {
            return Path.IsPathRooted(p)
                       ? p
                       : Path.Combine( Properties.Project.BaseDirectory, p );
        }

        protected override void ExecuteTask()
        {
            try
            {
                Log(string.Format( "Run as \"{0}\": {1}", Username, Program ));

                var process = Elevated(GetFullpath(Program), Commandline, Username, Password, WorkingDir ?? Properties.Project.BaseDirectory);
                process.WaitForExit();
            }
            catch
            {
                if (FailOnError)
                    throw;
            }
        }

        public static Process Elevated(string process, string args, string username, string password, string workingDirectory)
        {
            if (string.IsNullOrEmpty(process)) throw new ArgumentNullException("process");

            process = Path.GetFullPath(process);
            string domain = null;
            if (username != null)
                username = GetUsername(username, out domain);
            var info = new ProcessStartInfo();
            info.UseShellExecute = false;
            info.Arguments = args;
            info.WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(process);
            info.FileName = process;
            info.Verb = "runas";
            info.UserName = username;
            info.Domain = domain;
            info.LoadUserProfile = true;
            if (password != null)
            {
                var ss = new SecureString();
                foreach (char c in password)
                    ss.AppendChar(c);
                info.Password = ss;
            }

            return Process.Start(info);
        }

        private static string GetUsername(string username, out string domain)
        {
            SplitUserName(username, out username, out domain);

            if (domain == null && username.IndexOf('@') < 0)
                domain = Environment.GetEnvironmentVariable("USERDOMAIN");
            return username;
        }

        private static void SplitUserName(string username, out string s, out string domain)
        {
            var res = username.Split("\\".ToCharArray());
            domain = res[0];
            s = res[1];
        }
    }
}
